package magent

import (
	"encoding/json"
	"fmt"
	"log"
	"reflect"
	"time"

	"errors"

	"github.com/spf13/viper"
)

// sched to start scheduler job at start time by interval duration.
func sched(jobFunc interface{}, start, interval string, jobArgs ...interface{}) {
	jobValue := reflect.ValueOf(jobFunc)
	if jobValue.Kind() != reflect.Func {
		log.Panic("only function can be schedule.")
	}
	if len(jobArgs) != jobValue.Type().NumIn() {
		log.Panic("The number of args valid.")
	}
	// Get job function args.
	in := make([]reflect.Value, len(jobArgs))
	for i, arg := range jobArgs {
		in[i] = reflect.ValueOf(arg)
	}

	d, err := time.ParseDuration(interval)
	if err != nil {
		log.Panic(err)
	}
	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		log.Panic(err)
	}
	t, err := time.ParseInLocation("15:04:05", start, location)
	if err != nil {
		log.Panic(err)
	}
	now := time.Now()

	// Start time.
	t = time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), t.Second(), 0, location)

	if now.After(t) {
		t = t.Add((now.Sub(t)/d + 1) * d)
	}

	time.Sleep(t.Sub(now))
	go jobValue.Call(in)
	ticker := time.NewTicker(d)
	go func() {
		for _ = range ticker.C {
			go jobValue.Call(in)
		}
	}()
}

func postJob(urlSuffix string, notis interface{}, array bool) error {
	url := fmt.Sprintf("%s%s", viper.GetString("MonitorHost"), urlSuffix)
	var data interface{}
	data = notis
	if array {
		data = map[string]interface{}{"list": notis}
	}
	body, err := json.Marshal(data)
	if err != nil {
		return err
	}
	retry := 0
	for retry < 3 {
		resp, err := PostJSON(url, body)
		log.Println(nil, resp.StatusCode, len(body))
		if err != nil || resp.StatusCode != 200 {
			retry = retry + 1
		} else {
			break
		}
	}
	if retry >= 3 {
		return errors.New("Maxium retried.")
	}
	return nil
}

func postOpLogs() {
	opLogs, to, err := getOpLogs()
	if err != nil {
		log.Println("Cannot get oplogs.\n", err)
		return
	}

	if err := postJob("/logs", opLogs, true); err != nil {
		log.Println("Cannot send oplogs to monitor.\n", err)
	} else {
		if err := updateLast("oplog", to); err != nil {
			log.Println(err)
		} else {
			log.Println("Sended oplogs to montior.")
		}
	}
}

func postHealthNotis() {
	threshold := viper.GetInt("HealthThreshold")
	healthNotis := getHealthNotis(threshold)

	if err := postJob("/healths", healthNotis, true); err != nil {
		log.Println("Cannot send health notifications to monitor.\n", err)
	} else {
		log.Println("Sended health notifications to montior.")
	}
}

func postOffWT() {
	offWts, to, err := getOffWTs()
	if err != nil {
		log.Println("Cannot get offline windturbine info.\n", err)
		return
	}
	if err := postJob("/datas", offWts, true); err != nil {
		log.Println("Cannot send offline windturbine notifications to monitor.\n", err)
	} else {
		if err := updateLast("offline_todo", to); err != nil {
			log.Println(err)
		} else {
			log.Println("Sended offline windturbine notifications to montior.")
		}
	}
}

func postErrorScada() {
	errScada, err := getErrorScada()
	if err != nil {
		log.Println("Cannot get error scada notifications.\n", err)
		return
	}
	if err := postJob("/datas", errScada, true); err != nil {
		log.Println("Cannot send error scada notifications to monitor.\n", err)
	} else {
		log.Println("Sended error scada notifications to monitor.")
	}
}

func postServerStat() {
	serverStat, err := getServerStat()
	if err != nil {
		log.Println("Cannot get server status.\n", err)
	}
	if err := postJob("/services", serverStat, false); err != nil {
		log.Println("Cannot send server status to monitor.\n", err)
	} else {
		log.Println("Sended server status to monitor.")
	}
}

func postErrorLogs() {
	errLogs, to, err := getErrorLogs()
	if err != nil {
		log.Println("Cannot get error logs.\n", err)
		return
	}
	if err := postJob("/exceptions", errLogs, true); err != nil {
		log.Println("Cannot send error logs to monitor.\n", err)
	} else {
		if err := updateLast("error_log", to); err != nil {
			log.Println(err)
		} else {
			log.Println("Sended error logs to montior.")
		}
	}
}

// RunSched to run scheduler jobs.
func RunSched() {
	startTime := viper.GetString("SchedStart")
	interval := viper.GetString("SchedInterval")
	go sched(postOpLogs, startTime, viper.GetString("OpLogsInterval"))
	go sched(postHealthNotis, startTime, interval)
	go sched(postOffWT, startTime, interval)
	go sched(postErrorScada, startTime, viper.GetString("ScadaInterval"))
	go sched(postServerStat, startTime, viper.GetString("ServerInterval"))
	go sched(postErrorLogs, startTime, viper.GetString("ErrorLogInterval"))
}
